package com.team.coupon.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.team.coupon.domain.BizCoupon;
import com.team.coupon.domain.BizUserCoupon;
import com.team.coupon.domain.dto.BizConsumeCouponDto;
import com.team.coupon.domain.dto.BizUserCouponDto;
import com.team.coupon.exception.CustomException;
import com.team.coupon.mapper.BizCouponMapper;
import com.team.coupon.mapper.BizUserCouponMapper;
import com.team.coupon.service.BizUserCouponService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.team.coupon.util.RedisConstants.*;

@Slf4j
@Service
public class BizUserCouponServiceImpl implements BizUserCouponService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private BizUserCouponMapper bizUserCouponMapper;
    @Autowired
    private BizCouponMapper bizCouponMapper;
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 秒杀优惠券功能基本已完成
     * 抢购优惠券
     * @param bizUserCouponDto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String grabCoupon(BizUserCouponDto bizUserCouponDto) {
        //写一个random方法，随机生成一个10000-20000的数，测试接口用
        int random = (int) (Math.random() * 10000) + 10000;
        bizUserCouponDto.setUserId((long) random);
        //今天的日期
        String today = DateUtil.format(new Date(), DATE_FORMAT);
        String couponKey = BIZ_COUPON_KEY + today;
        Long couponId = bizUserCouponDto.getCouponId();
        String userGrabKey = USER_COUPON_SET_KEY + bizUserCouponDto.getUserId();
        String lockKey = USER_COUPON_LOCK_KEY + bizUserCouponDto.getUserId() + ":" + couponId;
        //声明Redisson锁对象
        RLock redissonClientLock = redissonClient.getLock(lockKey);

        boolean isLock = false;
        //标记Redis库存是否已扣减，用于回滚判断
        boolean isStockDecremented = false;
        //标记用户是否已经抢过该优惠券
        boolean isGrabCoupon = false;

        try{
            // 获取Redisson分布式锁， 参数：waitTime=5秒（获取锁的最大等待时间），leaseTime=-1（使用看门狗自动续期）
            isLock = redissonClientLock.tryLock(5 , -1 , TimeUnit.SECONDS);
            if (!isLock) {
                throw new CustomException("系统繁忙，请稍后再试");
            }

            //是否已经抢过该优惠券
            Long userAdd = redisTemplate.opsForSet().add(userGrabKey, couponId.toString());
            if (userAdd != 1) {
                throw new CustomException("您已经抢过该优惠券了");
            }
            isGrabCoupon = true;
            checkUserCouponExistsInDb(bizUserCouponDto , isGrabCoupon); // 是否是普通用户
            
            //判断优惠券是否存在
            if (!redisTemplate.opsForHash().hasKey(couponKey , couponId.toString())) {
                //不存在，返回错误信息,先去数据库查询
                BizCoupon bizCoupon = bizCouponMapper.selectCouponById(couponId , today);
                if (bizCoupon == null) {
                    //向redis里缓存空数据
                    // 数据库中也不存在，缓存空值
                    redisTemplate.opsForHash().put(couponKey, couponId.toString(), CACHE_NULL_VALUE);
                    throw new CustomException("优惠券不存在");
                }
                //存在，返回优惠券信息
                //向redis里缓存数据
                redisTemplate.opsForHash().put(couponKey, couponId.toString(), JSONUtil.toJsonStr(bizCoupon));
            }
            //判断是否是缓存的空值
            if (redisTemplate.opsForHash().get(couponKey, couponId.toString()).equals(CACHE_NULL_VALUE)) {
                throw new CustomException("优惠券不存在");
            }
            
            // 原子化预减库存 - 修复版本
            Long currentStock = (Long) redisTemplate.execute(
                    new DefaultRedisScript<>(
                            // Lua脚本：解析JSON → 扣减库存 → 回写JSON（原子操作）
                            "local couponJson = redis.call('HGET', KEYS[1], ARGV[1]) " +
                                    "if couponJson == false or couponJson == 'null' then return -1 end " +
                                    "local coupon = cjson.decode(couponJson) " +
                                    "if tonumber(coupon.stock) <= 0 then return -2 end " + // 先判断库存
                                    "coupon.stock = tonumber(coupon.stock) - 1 " +
                                    "redis.call('HSET', KEYS[1], ARGV[1], cjson.encode(coupon)) " +
                                    "return coupon.stock", // 返回扣减后的库存
                            Long.class
                    ),
                    Collections.singletonList(couponKey),  // 仅使用一个Hash键
                    couponId.toString()
            );
            
            if (currentStock == null) {
                throw new CustomException("系统繁忙，请稍后再试");
            }
            if (currentStock < 0) {
                if (currentStock == -1) {
                    throw new CustomException("优惠券不存在");
                } else if (currentStock == -2) {
                    throw new CustomException("优惠券已抢光");
                }
                throw new CustomException("优惠券库存不足");
            }
            
            //标记Redis库存已扣减
            isStockDecremented = true;
            
            // 数据库层面再次验证库存并扣减
            int updateResult = bizCouponMapper.updateCouponStock(couponId);
            if (updateResult == 0) {
                throw new CustomException("优惠券库存不足，请稍后再试");
            }
            
            addUserCoupon(bizUserCouponDto);
            return "领取成功";
        }catch (CustomException e){
            if (isStockDecremented) {
                // 回滚库存
                rollbackStock(couponKey, couponId);
            }
            if (isGrabCoupon) {
                // 回滚用户抢券记录
                redisTemplate.opsForSet().remove(userGrabKey, couponId.toString());
            }
            log.error("抢券失败: {}", e.getMsg());
            throw new CustomException(e.getMsg());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            if (isLock && redissonClientLock.isHeldByCurrentThread()) {
                redissonClientLock.unlock();
            }
        }
    }
    //添加用户优惠券到数据库
    void addUserCoupon(BizUserCouponDto bizUserCouponDto){
        //用糊涂工具将dto转换成bizUserCoupon
        BizUserCoupon bizUserCoupon = BeanUtil.toBean(bizUserCouponDto, BizUserCoupon.class);
        //设置优惠券状态为未使用
        bizUserCoupon.setStatus("0");
        //设置优惠券的领取时间为当前时间  时间格式为yyyy-MM-dd HH:mm:ss
        bizUserCoupon.setReceiveTime(new Date());
        //插入数据库
        bizUserCouponMapper.insert(bizUserCoupon);
    }
    //判断用户是否是普通用户，是否已经抢过该优惠券
    void checkUserCouponExistsInDb(BizUserCouponDto bizUserCouponDto, boolean isGrabCoupon){
        // TODO 判断该用户是否是普通用户 需要远程调用

        //查询数据库中是否存在该用户的该优惠券
        int bizUserCoupon = bizUserCouponMapper.selectUserCouponByUserIdAndCouponId(bizUserCouponDto.getUserId(), bizUserCouponDto.getCouponId());
        if (bizUserCoupon != 0) {
            // 如果数据库中已存在，将用户标记为已抢过该优惠券
            redisTemplate.opsForSet().add(USER_COUPON_SET_KEY + bizUserCouponDto.getUserId(), bizUserCouponDto.getCouponId().toString());
            throw new CustomException("您已经抢过该优惠券了");
        }
    }
    //使用lua脚本回滚redis库存
    void rollbackStock(String couponKey, Long couponId) {
        // 执行Lua脚本回滚库存
        redisTemplate.execute(
                new DefaultRedisScript<>(
                                "local couponJson = redis.call('HGET', KEYS[1], ARGV[1]) " +
                                "if couponJson == false or couponJson == 'null' then return end " +
                                "if couponJson == false then return end " +
                                "local coupon = cjson.decode(couponJson) " +
                                "coupon.stock = tonumber(coupon.stock) + 1 " +  // 库存加1回滚
                                "redis.call('HSET', KEYS[1], ARGV[1], cjson.encode(coupon))",
                        Void.class
                ),
                Collections.singletonList(couponKey),
                couponId.toString()
        );
    }


    /**
     * 根据用户id和发布主体id查询在该商铺可以使用的优惠券
     * @param userId
     * @param entityId
     * @return
     */
    @Override
    public List<BizUserCoupon> listCouponsByUserIdAndEntityId(Long userId, Long entityId) {
        List<BizUserCoupon> bizUserCoupons = bizUserCouponMapper.selectCouponsByUserIdAndEntityId(userId, entityId);
        //过滤掉已经过期的优惠券
        bizUserCoupons = bizUserCoupons.stream().filter(bizUserCoupon -> bizUserCoupon.getValidEnd().after(new Date())).collect(Collectors.toList());
        return bizUserCoupons;
    }

    /**
     * 消费优惠券
     * @param bizConsumeCouponDto
     * @return
     */
    @Override
    public String consumeCoupon(BizConsumeCouponDto bizConsumeCouponDto) {
        // TODO 校验用户是否是普通用户 需要远程调用
        // 查询优惠券信息
        BizUserCoupon bizUserCoupon = bizUserCouponMapper.selectByPrimaryKey(bizConsumeCouponDto.getCouponId());
        if (bizUserCoupon == null) {
            throw new CustomException("优惠券不存在");
        }
        // 检查优惠券是否已被使用
        if (bizUserCoupon.getStatus().equals("1")) {
            throw new CustomException("优惠券已被使用");
        }
        //检查优惠券归属是否正确
        if (!bizUserCoupon.getUserId().equals(bizConsumeCouponDto.getUserId())) {
            throw new CustomException("优惠券归属不正确");
        }
        // 可以使用优惠券
        bizUserCoupon.setStatus("1");
        // 更新数据库
        int updateResult = bizUserCouponMapper.updateByPrimaryKeySelective(bizUserCoupon);

        if (updateResult == 0) {
            throw new CustomException("优惠券使用失败，请稍后再试");
        }
        return "优惠券使用成功";
    }

    /**
     * 查看用户的优惠券
     * @param userId
     * @return
     */
    @Override
    public List<BizUserCoupon> listCouponsByUserId(Long userId) {
        //List<BizUserCoupon> bizUserCoupons = bizUserCouponMapper.selectCouponsByUserId(userId);
        HashMap<String, BizCoupon> bizCouponMap = new HashMap<>();




        bizCouponMap.put("unUsedCoupon" , null);
        bizCouponMap.put("usedCoupon", null);
        bizCouponMap.put("expiredCoupon", null);
        return null;
        //return bizCouponMap;

    }
}
